﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using BesAsm.Emgaats.Core;
using BesAsm.Framework.MsAccessUtilities;
using BesAsm.Framework.Reporting;
using BesAsm.MasterData.Entities;
using BesAsm.MasterData.Services;

namespace BesAsm.Emgaats.ModelBuilder
{
  /// <summary>
  /// Provides methods to write model hydrology files
  /// </summary>
  public class HydrologyBuilder
  {
    private Model _model;
    private List<Simulation> _simulations;
    private string _dbPath;
    private string _codeDbPath;
    private AccessHelper _accessHelper;

    private const string MODEL_DEPLOY_HYDROLOGY_ACCESS_DB = "ModelDeployHydrology.mdb";
    private const string EMGAATS_CODE_ACCESS_DB = "EmgaatsCode.mdb";
    /// <summary>
    /// Initializes a new instance of the <see cref="HydrologyBuilder"/> class.
    /// </summary>
    /// <param name="model">The model to deploy. The state of this model must be 
    /// at least ModelState.Assembled, or a ModelStateException will be thrown.</param>
    public HydrologyBuilder(Model model)
    {      
      if (model.State < ModelState.Assembled)
        throw new ModelStateException(model.State, ModelState.Assembled, "Unable to create hydrology builder: Model not yet built.");

      _model = model;
      _simulations = model.Simulations;
      _dbPath = Path.Combine(model.GetPathIfSet(ModelPathComponent.RootFolder), MODEL_DEPLOY_HYDROLOGY_ACCESS_DB);
      _codeDbPath = Path.Combine(model.GetPathIfSet(ModelPathComponent.RootFolder), EMGAATS_CODE_ACCESS_DB);
      WriteHydrologyDb();
    }

    private void WriteHydrologyDb()
    {
      WriteModelDeployHydrology();
      WriteEmgaatsCode();
    }

    private void WriteModelDeployHydrology()
    {
      Messenger.ReportMessage("Copying bare ModelDeployHydrology", ReportableMessageType.Info);

      FileStream outStream = null;
      try
      {
        outStream = new FileStream(_dbPath, FileMode.Create);
        byte[] data = Properties.Resources.ModelDeployHydrology;
        outStream.Write(data, 0, data.Length);
        outStream.Flush();
      }
      catch (Exception ex)
      {
        //TODO: Code crashes ungracefully here if ModelDeployHydrology is locked, need to send message to UI
      }
      finally
      {
        if (outStream != null)
          outStream.Close();
      }
    }

    private void WriteEmgaatsCode()
    {
      Messenger.ReportMessage("Copying emgaatscode", ReportableMessageType.Info);

      FileStream outStream = null;
      try
      {
        outStream = new FileStream(_codeDbPath, FileMode.Create);
        byte[] data = Properties.Resources.EmgaatsCode;
        outStream.Write(data, 0, data.Length);
        outStream.Flush();
      }
      catch (Exception ex)
      {
        //TODO: Code crashes ungracefully here if EmgaatsCode is locked, need to send message to UI
      }
      finally
      {
        if (outStream != null)
          outStream.Close();
      }
    }


    /// <summary>
    /// Deploys the model hydrology file
    /// </summary>
    /// <param name="hydraulicsFile">The hydrology file to deploy.</param>
    public void DeployHydrology()
    {

      string modelPath = _model.GetPathIfSet(ModelPathComponent.Pgdb);

      _accessHelper = null;
      try
      {
        _accessHelper = new AccessHelper(_dbPath);

        PrepareHydrologyTables(modelPath);

        Messenger.ReportMessage("Calculating runoff", ReportableMessageType.Info);
        _accessHelper.ExecuteQueryTable("EMG3_01_Execute", "QueryName", "dumpOrder");

        foreach (Simulation simulation in _simulations)
        {
          ExportHydrologyFile(simulation);
        }
      }
      finally
      {
        if (_accessHelper != null)
          _accessHelper.Dispose();
      }

    }

    private void ExportHydrologyFile(Simulation simulation)
    {
      StormService stormService = new StormService();
      Storm storm = stormService.GetByStormId(simulation.Id);
      Messenger.ReportMessage("Updating tables for export of storm " + storm.Abbreviation, ReportableMessageType.Info);
      PrepareForStormExport(simulation);

      // TODO: Copy rainfall file

      Messenger.ReportMessage("Exporting runoff file to " + simulation.HydrologyFile, ReportableMessageType.Info);
      _accessHelper.ExportQueryTable("EMG3_02_Export", "QueryName", "dumpOrder", simulation.HydrologyFile, FileType.Raw);
    }

    private void PrepareHydrologyTables(string modelPath)
    {
      Messenger.ReportMessage("Linking tables", ReportableMessageType.Info);

      _accessHelper.UpdateField("TimeFrame", "[value]", 
        _model.TimeFrame.ToUpper() == "EXISTING" ? "EX" : "FU");
      _accessHelper.LinkTable("MODEL_DSCS", modelPath, "EMG3_DSCS", ExternalDatabaseType.MsAccess);
      _accessHelper.LinkTable("MODEL_IC_DISCOS", modelPath, "EMG3_IC_DISCOS", ExternalDatabaseType.MsAccess);
      _accessHelper.LinkTable("MODEL_IC_DRYWELLS", modelPath, "EMG3_IC_DRYWELLS", ExternalDatabaseType.MsAccess);
      _accessHelper.LinkTable("MODEL_IC_NODE_REDIRECTORS", modelPath, "EMG3_IC_NODE_REDIRECTORS", ExternalDatabaseType.MsAccess);
      _accessHelper.LinkTable("MODEL_LINKS", modelPath, "EMG3_LINKS", ExternalDatabaseType.MsAccess);
      _accessHelper.LinkTable("MODEL_NODES", modelPath, "EMG3_NODES", ExternalDatabaseType.MsAccess);
      _accessHelper.LinkTable("MODEL_SSCS", modelPath, "EMG3_SSCS", ExternalDatabaseType.MsAccess);

      _accessHelper.LinkTable("MODEL_DSCS_CALCULATED", modelPath, "EMG3_DSCS_CALCULATED", ExternalDatabaseType.MsAccess);
      _accessHelper.LinkTable("MODEL_SSCS_CALCULATED", modelPath, "EMG3_SSCS_CALCULATED", ExternalDatabaseType.MsAccess);
    }

    private void PrepareForStormExport(Simulation simulation)
    {
      UpdateTitleFields(simulation);
      UpdateStormFields(simulation);
    }

    private void UpdateTitleFields(Simulation simulation)
    {
      _accessHelper.UpdateField("R_CONTROL_A1", "[TitleText]", "[TwoRecordsOnly] = 1", simulation.Title1);
      _accessHelper.UpdateField("R_CONTROL_A1", "[TitleText]", "[TwoRecordsOnly] = 2", simulation.Title2);
    }

    private void UpdateStormFields(Simulation simulation)
    {
      StormService stormService = new StormService();
      Storm storm = stormService.GetByStormId(simulation.Id);
      _accessHelper.UpdateField("R_CONTROL_B1", "[DesignStorm?]", storm.Type == "D");
      _accessHelper.UpdateField("R_CONTROL_B1", "[NHR]", storm.StartDate.Value.Hour);
      _accessHelper.UpdateField("R_CONTROL_B1", "[NMN]", storm.StartDate.Value.Minute);
      _accessHelper.UpdateField("R_CONTROL_B1", "[NDAY]", storm.StartDate.Value.Day);
      _accessHelper.UpdateField("R_CONTROL_B1", "[MONTH]", storm.StartDate.Value.Month);
      _accessHelper.UpdateField("R_CONTROL_B1", "[IYRSTR]", storm.StartDate.Value.Year);
      _accessHelper.UpdateField("R_Control_Files", "[FILENAM]", "[FILENUM] = 11", storm.Filename);
      _accessHelper.UpdateField("R_CONTROL_B3", "[Wet]", storm.WetTimeStep);
      _accessHelper.UpdateField("R_CONTROL_B3", "[WetDry]", storm.WetToDryTimeStep);
      _accessHelper.UpdateField("R_CONTROL_B3", "[Dry]", storm.DryTimeStep);
      _accessHelper.UpdateField("R_CONTROL_B3", "[LUNIT]", 2);
      _accessHelper.UpdateField("R_CONTROL_B3", "[Long]",
        Convert.ToInt32(Math.Ceiling(storm.EndDate.Value.Subtract(
          storm.StartDate.Value).TotalHours)));
    } 
  }
}
